package lpg;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
import lpg.util.DispatchWriter;
import lpg.util.Utility;

class Grammar
{
    Grammar(Control control)
    {
        this.control        = control;
        this.option         = control.option;
        this.lexStream      = control.lexStream;
        this.actionBlocks   = control.option.actionBlocks();
        this.variableTable  = control.variableTable;
        this.macroTable     = control.macroTable;
        this.parser         = new Parser(control, lexStream, variableTable, macroTable);

        this.symbolIndex    = new ArrayList<SymbolElement>(variableTable.size());
        this.name           = new ArrayList<String>(variableTable.size());

        nullSymbol          = new VariableSymbol("", 0);

        emptySymbol         = allocateVariableSymbol("empty");
        acceptSymbol        = allocateVariableSymbol("accept");
    }

    void processInput(Action action)
    {
        parser.parse();
        if (parser.rules.isEmpty())
        {
            option.emitError(lexStream.numTokens() - 2, // point to the last token
                    option.grmFile + " is an empty grammar... Processing stopped");

            control.exit();
        }

        //
        // Add all the exported symbols to the export_macro_table.
        //
        action.insertExportMacros(); // make sure that this is processed first
        action.insertImportedFilterMacros();
        action.checkMacrosForConsistency();

        //
        // If bad errors were detected, quit!
        //
        if (action.errorFlag)
            control.exit();

        symbolIndex.add(null); // skip the 0th element

        processTerminals();
        processRules();
        processExportedTerminals();
        processNames();

        for (int i = 0, n = parser.predecessorCandidates.size(); i < n; i++)
        {
            PredecessorSetDefinition predecessorCandidate = parser.predecessorCandidates.get(i);
            int lhsIndex = predecessorCandidate.lhsIndex;
            int rhsIndex = predecessorCandidate.rhsIndex;

            PairElement elem = new PairElement(lexStream.getVariableSymbol(lhsIndex).symbolIndex(),
                                               lexStream.getVariableSymbol(rhsIndex).symbolIndex());
            checkPredecessorSetsFor.add(elem);

            if (elem.leftSymbol == 0)
                option.emitError(lhsIndex, "This symbol was not defined in the grammar");
            if (elem.rightSymbol == 0)
                option.emitError(rhsIndex, "This symbol was not defined in the grammar");
        }

        option.flushReport();
    }

    //
    // Remove names assigned to nonterminals that are never used as
    // error candidates and only create prostheses for the useful
    // nonterminals.
    //
    void optimizeNames(Base base, Produce produce)
    {
        //
        // The "name" and "prostheses" maps are only needed for error recovery.
        //
        if (!(option.errorMaps && option.table && option.names == Option.Names.OPTIMIZED))
            return;

        //
        // In addition to nonterminals that are never used as candidates,
        // if a nullable nonterminal was assigned a name by default
        // (nonterminals that were "named" by default are identified
        // with negative indices), that name is also removed.
        //
        for (int symbol = firstNonTerminal(); symbol <= lastNonTerminal(); symbol++)
        {
            SymbolElement symbolElement = symbolIndex.get(symbol);
            int acceptIndex = symbolIndex.get(acceptImage).externalNameIndex;
            if (!produce.isUseful(symbol))
                symbolElement.externalNameIndex = acceptIndex;
            else if (symbolElement.externalNameIndex < 0)
            {
                if (base.isNullable(symbol))
                    symbolElement.externalNameIndex = acceptIndex;
                else
                    symbolElement.externalNameIndex = -symbolElement.externalNameIndex;
            }
        }

        //
        // Adjust name map to remove unused elements and update SYMNO map.
        //
        //
        // First, compute the set of names that are useful
        //
        boolean[] nameUsed = new boolean[numNames + 1];
        for (int symbol = 1; symbol <= numSymbols; symbol++)
            nameUsed[symbolIndex.get(symbol).externalNameIndex] = true;

        //
        // Next, construct a new name map that only contains the useful names
        //
        List<String> newNames = new ArrayList<String>();
        int[] namesMap = new int[numNames + 1];
        for (int i = 0; i <= numNames; i++)
        {
            if (nameUsed[i])
            {
                namesMap[i] = newNames.size();
                newNames.add(name.get(i));
            }
        }

        //
        // Next, save the new map permanently
        //
        numNames = newNames.size() - 1;
        name = newNames;

        //
        // Finally, remap each symbol to point to its new name.
        //
        for (int symbol = 1; symbol <= numSymbols; symbol++)
        {
            SymbolElement symbolElement = symbolIndex.get(symbol);
            symbolElement.externalNameIndex = namesMap[symbolElement.externalNameIndex];
        }
    }

    //
    // This function checks whether or not classname_index points to a real name.
    // If so, it returns classname_index. Otherwise, if classname_index identifies
    // the null name, $, or does not point to any name (classname_index = 0), then
    // it creates a name for it and returns the index of that name.
    //
    private int getProsthesisNameIndex(int lhsIndex, int lhsImage, int classnameIndex)
    {
        //
        // if classname_index is already a valid name, return it.
        //
        if (classnameIndex == 0) // || lex_stream -> NameStringLength(classname_index) == 1)
        {
            String macroName = option.escape + retrieveString(lhsImage) + "Prosthesis";
            MacroSymbol macroSymbol = macroTable.find(macroName);
            if (macroSymbol == null)
            {
                macroSymbol = new MacroSymbol(macroName, macroTable.size());
                macroTable.insert(macroName, macroSymbol);
            }
            else
            {
                option.emitWarning(lhsIndex, "\"" + name + "\" was previously used as a classname for a rule");
            }

            Token locToken = lexStream.getTokenReference(classnameIndex > 0 ? classnameIndex : lhsIndex);

            classnameIndex = lexStream.getNextToken(locToken.fileSymbol(), locToken.startLocation());

            Token classnameToken = lexStream.getTokenReference(classnameIndex);
            classnameToken.setEndLocation(locToken.endLocation());
            classnameToken.setKind(Lpgsym.TK_MACRO_NAME);
            classnameToken.setSymbol(macroSymbol);
        }

        return classnameIndex;
    }

    private int getClassnameIndex(int ruleIndex, int lhsIndex, int classnameIndex, int arrayElementTypeIndex)
    {
        int lhsImage = rules.get(ruleIndex).lhs;

        String name = (classnameIndex > 0 ? lexStream.nameString(classnameIndex) : retrieveString(lhsImage));

        if (Utility.checkQualifiedIdentifier(name) != 0)
        {
            option.emitError(classnameIndex > 0 ? classnameIndex : lhsIndex,
                    "\"" + name + "\" is an illegal variable name");
            errorFlag = true;
        }

        //
        // If we are dealing with an array (typed or untyped) without an explicit
        // classname specified for it, we compose its name here. Otherwise, we accept
        // "name" as the name of the class.
        //
        Symbol symbol = null;
        VariableSymbol arrayElementTypeSymbol = lexStream.getVariableSymbol(arrayElementTypeIndex);
        if (arrayElementTypeIndex != 0 && lexStream.nameString(classnameIndex).length() == 1) // The classname is the null macro?
        {
            String arrayElementName = arrayElementTypeSymbol.name().length() > 0
                                      ? arrayElementTypeSymbol.name()
                                      : option.astType;
            String listName = option.escape + arrayElementName + "List";
            symbol = classnameSet.findOrInsert(listName, new Symbol(listName, classnameSet.size()));
        }
        else
        {
            //
            // Check whether or not the user-specified name for a classname clashes
            // with a nonterminal of the same name that will also be used as a classname.
            // If so, reuse the previous name and issue a warning.
            //
            if (classnameIndex > 0)
            {
                symbol = classnameSet.find(name.substring(1));
                if (symbol == null)
                    symbol = classnameSet.findOrInsert(name, new Symbol(name, classnameSet.size()));
                else
                    option.emitWarning(classnameIndex,
                            "The class name \"" + symbol.name() + "\" was implicitly used previously as a class name");
            }
            else
            {
                symbol = classnameSet.find(option.escape + name);
                if (symbol == null)
                    symbol = classnameSet.findOrInsert(name, new Symbol(name, classnameSet.size()));
                else
                    option.emitWarning(lhsIndex,
                            "This name \"" + name + "\" was explicitly used previously as a class name");
            }
        }

        int index = symbol.index();
        if (index == classname.size())
        {
            classname.add(new ClassnameElement(symbol.name(),
                    symbol.name().charAt(0) == option.escape ? symbol.name().substring(1) : symbol.name(),
                    arrayElementTypeIndex == 0 ? null : arrayElementTypeSymbol));
        }
        else if (arrayElementTypeIndex != 0 &&
                 arrayElementTypeSymbol.name().length() > 0)
        {
            if (classname.get(index).arrayElementTypeSymbol != arrayElementTypeSymbol)
            {
                option.emitError(classnameIndex, "Respecification of an array with a different element type");
                errorFlag = true;
            }
        }

        //
        // If we are dealing with an array (typed or untyped) without an explicit
        // classname specified for the nonterminal in question and an action block
        // is present then we will be creating a unique subclass of the generic list
        // for this nonterminal. Here, we keep track of such instances.
        //
        IntArrayList spNtArray = specialNonterminalArray[arrayElementTypeSymbol.index()];
        if (spNtArray.contains(lhsImage)) // add nonterminal name to make class unique
        {
            List<SpecialArrayElement> specialArrays = classname.get(index).specialArrays;
            SpecialArrayElement specialArray = null;
            for (int i = 0, n = specialArrays.size(); i < n; i++)
            {
                if (specialArrays.get(i).lhsImage == lhsImage)
                {
                    specialArray = specialArrays.get(i);
                    break;
                }
            }
            if (specialArray != null)
                specialArray.rules.add(ruleIndex);
            else // first encounter of lhs_image
            {
                String lhsName = retrieveString(lhsImage);
                String extendedClassname = lhsName + '_' + classname.get(index).realName;
                specialArray = new SpecialArrayElement(lhsImage, extendedClassname);
                specialArray.rules.add(ruleIndex);
                specialArrays.add(specialArray);
            }
        }

        return index;
    }

    void processActions(Action action, Base base, Produce produce)
    {
        optimizeNames(base, produce);

        //
        // If we will be generating Ast classes, we update the classname info for the
        // user-specified rules.
        //
        // The array list_nonterminals keeps track of nonterminals who are the left-hand
        // side of rules that are associated with one list classname. If the value of
        // list_nonterminals[A] is negative then A is then not all the rules of which
        // A is the left-hand side are associated with the same list; If the value is 0
        // then no determination has been made yet (initial value); Otherwise, the value
        // identifies the index (in classname) of the list that is associated with all
        // rules that have A as their left-hand side.
        //
        BoundedIntArray listNonterminals = new BoundedIntArray(firstNonTerminal(), lastNonTerminal());
        if (option.automaticAst != Option.AutomaticAst.NONE)
        {
            //
            // Process the Token classname.
            //
            String astTokenClassName = option.astType + "Token";
            classnameSet.findOrInsert(astTokenClassName, new Symbol(astTokenClassName, classnameSet.size()));

            classname.add(new ClassnameElement(astTokenClassname, astTokenClassname, null, true));

            //
            // Note that we skip the start rules as No AST is generated for them.
            // We might generate more classnames later for prostheses rules...
            //
            for (int ruleIndex = startSymbol.size(); ruleIndex <= numRules; ruleIndex++)
            {
                RuleDefinition rule = parser.rules.get(rules.get(ruleIndex).sourceIndex);
                int lhsImage = rules.get(ruleIndex).lhs;
                int index = getClassnameIndex(ruleIndex, rule.lhsIndex, rule.classnameIndex, rule.arrayElementTypeIndex);

                if (classname.get(index).arrayElementTypeSymbol != null &&
                        (listNonterminals.get(lhsImage) == 0 || listNonterminals.get(lhsImage) == index))
                    listNonterminals.set(lhsImage, index);
                else
                    listNonterminals.set(lhsImage, -1);

                //
                // Process rules that are explicitly associated with a non-null classname
                // or are not empty rules or are not single productions with a nonterminal
                // on the right-hand side and no semantic actions.
                //
                int ruleSize = rhsSize(ruleIndex); // rhsSym.size() - firstRhsIndex(ruleIndex);
                if (ruleSize > 1 ||
                        (!classname.get(index).specifiedName.equals(classname.get(index).realName)) ||
                        (ruleSize == 1 && (isTerminal(rhsSym.get(firstRhsIndex(ruleIndex))) ||
                                           lexStream.kind(lexStream.previous(rule.endRhsIndex)) == Lpgsym.TK_BLOCK)))
                    classname.get(index).rule.add(ruleIndex);
                else
                    classname.get(index).ungeneratedRule.add(ruleIndex);
            }
        }

        //
        // Process the prostheses here. We start by preparing the rules strunctures
        // so that they can be extended.
        //
        int saveParserRulesLength = parser.rules.size();
        int saveRulesLength = rules.size();
        int saveRhsSymLength = rhsSym.size();
        if (option.errorMaps)
        {
            rules.remove(saveRulesLength - 1); // remove the previous gate

            prosthesisRuleIndex = new int[numSymbols + 1];
            //
            // Assign a new rule number to each nonterminal for which a prosthesis is explicitly requested.
            //
            StringBuilder tok = new StringBuilder(Control.SYMBOL_SIZE);
            for (int i = 0, n = parser.prostheses.size(); i < n; i++)
            {
                RuleDefinition prosthesis = parser.prostheses.get(i);
                List<ProcessedRuleElement> processedRuleElements = new ArrayList<ProcessedRuleElement>();
                processedRuleMap.add(processedRuleElements);
                VariableSymbol lhsSymbol = lexStream.getVariableSymbol(prosthesis.lhsIndex);
                int lhsImage = lhsSymbol.symbolIndex();
                if (lhsImage == 0)
                {
                    option.emitError(prosthesis.lhsIndex,
                            "Prosthesis requested for undeclared nonterminal: " + lhsSymbol.name());
                    errorFlag = true;
                    continue;
                }

                if (isTerminal(lhsImage))
                {
                    restoreSymbol(tok, retrieveString(lhsImage));
                    option.emitError(prosthesis.lhsIndex,
                            "Terminal symbol \"" + tok + "\" used the left-hand side of a rule");
                    errorFlag = true;
                    continue;
                }

                if (prosthesisRuleIndex[lhsImage] != 0)
                {
                    restoreSymbol(tok, retrieveString(lhsImage));
                    option.emitWarning(prosthesis.lhsIndex,
                            "Respecification of a prosthesis for nonterminal \"" + tok + "\" ignored");
                    continue;
                }

                if (symbolIndex.get(lhsImage).externalNameIndex == symbolIndex.get(acceptImage).externalNameIndex)
                {
                    restoreSymbol(tok, retrieveString(lhsImage));
                    option.emitWarning(prosthesis.lhsIndex,
                            "It is not necessary to specify a prosthesis for nonterminal \"" + tok + "\"");
                }

                prosthesis.classnameIndex =
                        getProsthesisNameIndex(prosthesis.lhsIndex, lhsImage, prosthesis.classnameIndex);
                parser.rules.add(prosthesis);

                prosthesisRuleIndex[lhsImage] = rules.size();
                rules.add(new RuleElement());
            }

            //
            // If the option "prostheses = MAXIMUM" is specified, make sure that
            // all nonterminal that require a prosthesis are processed.
            //
            if (option.prostheses != Option.Prostheses.MINIMUM)
            {
                for (int lhsImage = firstNonTerminal(); lhsImage <= lastNonTerminal(); lhsImage++)
                {
                    //
                    // The MAXIMUM set includes all nonterminals that might be used in a recovery.
                    // The OPTIMIZED set excludes the nullable nonterminals from the MAXIMUM set.
                    // The idea being that for a nullable nonterminal, you can simply amputate
                    // (throw away the erroneous tokens) without inserting a prosthesis (that spans
                    // the erroneous tokens).
                    //
                    if (prosthesisRuleIndex[lhsImage] == 0 && produce.isUseful(lhsImage) &&
                            (option.prostheses == Option.Prostheses.MAXIMUM || !base.isNullable(lhsImage)))
                    {
                        int ruleNo = rulesOfLhs(lhsImage).get(0); // the first rule specified for lhs_image
                        int sourceIndex = rules.get(ruleNo).sourceIndex;
                        if (option.automaticAst != Option.AutomaticAst.NONE) // automatically create prosthesis.
                        {
                            RuleDefinition rule = parser.rules.get(sourceIndex);
                            RuleDefinition prosthesis = new RuleDefinition(
                                    rule.lhsIndex, rule.separatorIndex, lexStream.next(rule.separatorIndex));
                            parser.rules.add(prosthesis);
                            if (listNonterminals.get(lhsImage) > 0) // nonterminal associated with a list?
                            {
                                prosthesis.classnameIndex = rule.classnameIndex;
                                prosthesis.arrayElementTypeIndex = rule.arrayElementTypeIndex;
                            }
                            else
                            {
                                prosthesis.classnameIndex = getProsthesisNameIndex(prosthesis.lhsIndex, lhsImage, 0);
                                prosthesis.arrayElementTypeIndex = 0;
                            }

                            prosthesisRuleIndex[lhsImage] = rules.size();
                            rules.add(new RuleElement());
                        }
                        else
                        {
                            restoreSymbol(tok, retrieveString(lhsImage));
                            option.emitWarning(parser.rules.get(sourceIndex).lhsIndex,
                                    "No prosthesis specified for \"" + tok + "\"");
                        }
                    }
                }
            }

            //
            // Process user specified prostheses
            //
            for (int i = saveParserRulesLength, n = parser.rules.size(); i < n; i++)
            {
                RuleDefinition rule = parser.rules.get(i);
                VariableSymbol lhsSymbol = lexStream.getVariableSymbol(rule.lhsIndex);
                int lhsImage = lhsSymbol.symbolIndex();
                int ruleIndex = prosthesisRuleIndex[lhsImage];

                assert (ruleIndex != 0);

                List<ProcessedRuleElement> processedRuleElements = new ArrayList<ProcessedRuleElement>();
                processedRuleMap.add(processedRuleElements);

                //
                // Point to the last token in the prosthesis.
                //
                int lastSymbolIndex = lexStream.previous(rule.endRhsIndex);
                if (lexStream.kind(lastSymbolIndex) == Lpgsym.TK_BLOCK)
                    lastSymbolIndex = lexStream.previous(lastSymbolIndex);

                //
                // Add info for this prosthesis to rules (of RuleElement) array.
                //
                RuleElement ruleElement = rules.get(ruleIndex);
                ruleElement.firstTokenIndex = rule.separatorIndex;
                ruleElement.lastTokenIndex = lastSymbolIndex;
                ruleElement.lhs = lhsImage;
                ruleElement.separatorTokenKind = 0;
                ruleElement.rhsIndex = rhsSym.size();
                ruleElement.sourceIndex = i;
                ruleElement.producesTokenKind = 0;

                int j = lexStream.previous(rule.endRhsIndex);
                if (lexStream.kind(j) == Lpgsym.TK_BLOCK)
                {
                    BlockSymbol block = lexStream.getBlockSymbol(j);
                    if (!option.actionBlocks().isIgnoredBlock(block.blockBegin()))
                    {
                        StringBuilder buffer = block.buffer();
                        ActionBlockElement actionBlockElement = new ActionBlockElement(ruleIndex,
                                ActionBlockElement.ActionBlockKind.BODY, j,
                                buffer != null ? buffer : block.actionFileSymbol().bodyBuffer());
                        if (option.automaticAst != Option.AutomaticAst.NONE)
                            astActions.add(actionBlockElement);
                        else
                            codeActions.add(actionBlockElement);
                    }
                }
                else if (option.automaticAst == Option.AutomaticAst.NONE) // not automatic AST? must supply code to build prosthesis
                {
                    restoreSymbol(tok, retrieveString(lhsImage));
                    option.emitError(rule.lhsIndex, "No action specified to create prosthesis for \"" + tok + "\"");
                    errorFlag = true;
                }
            }

            //
            // add a gate marker for the last rule.
            //
            RuleElement ruleElement = new RuleElement();
            ruleElement.rhsIndex = rhsSym.size();
            rules.add(ruleElement);

            //
            // If we will be generating Ast classes, we update the classname info for the prostheses.
            //
            if (option.automaticAst != Option.AutomaticAst.NONE)
            {
                //
                // For each prosthesis rule, process its class.
                // Note that we go up to (rules.Length - 1) to avoid the gate element.
                //
                for (int ruleIndex = numRules + 1, n = rules.size() - 1; ruleIndex < n; ruleIndex++)
                {
                    int sourceIndex = rules.get(ruleIndex).sourceIndex;
                    RuleDefinition rule = parser.rules.get(sourceIndex);
                    int index = getClassnameIndex(ruleIndex, rule.lhsIndex, rule.classnameIndex, rule.arrayElementTypeIndex);
                    classname.get(index).rule.add(ruleIndex);
                }
            }
        }

        //
        // If automatic generation of the AST nodes is requested, we generate
        // the typestring map. Otherwise, we construct the typestring map based
        // on information provided by the user in the Type section of his grammar.
        // Note that this array has an upper bound of "num_symbols + 1" instead
        // of "num_symbols". This is because the index "num_symbols + 1" will be
        // used to represent the interface associated with Tokens.
        //
        LookupTable<String, Symbol> symbolSet = new LookupTable<String, Symbol>();
        astTokenInterface = numSymbols + 1;
        String[] typestring = new String[astTokenInterface + 1];
        if (option.automaticAst != Option.AutomaticAst.NONE)
        {
            typestring[0] = option.astType;

            typestring[astTokenInterface] = insertInterface(symbolSet, astTokenClassname);
            for (int symbol = 1; symbol <= numTerminals; symbol++)
                typestring[symbol] = typestring[astTokenInterface];

            for (int symbol = numTerminals + 1; symbol <= numSymbols; symbol++)
                typestring[symbol] = insertInterface(symbolSet, retrieveString(symbol));
        }
        else if (!parser.types.isEmpty())
        {
            String terminalDefaultType = action.getDefaultTerminalType();
            String nonterminalDefaultType = action.getDefaultNonterminalType();
            if (terminalDefaultType == null || nonterminalDefaultType == null)
            {
                option.emitError(parser.types.get(0).typeIndex,
                        "Type information cannot be processed for this programming language");
                errorFlag = true;
            }

            typestring[0] = terminalDefaultType;
            Arrays.fill(typestring, 1, numTerminals + 1, terminalDefaultType);

            typestring[acceptImage] = nonterminalDefaultType;
            for (int i = 0, n = parser.types.size(); i < n; i++)
            {
                TypeDefinition type = parser.types.get(i);
                VariableSymbol symbol = getSymbol(type.symbolIndex);
                int image = (symbol != null ? symbol.symbolIndex() : 0);
                if (image == 0)
                {
                    option.emitError(type.symbolIndex, "Undefined nonterminal symbol");
                    errorFlag = true;
                }

                if (option.warnings && typestring[image] != null)
                    option.emitWarning(type.symbolIndex, "Duplicate specification of this type");

                typestring[image] = lexStream.nameString(type.typeIndex);
            }

            IntArrayList untyped = new IntArrayList();
            for (int symbol = numTerminals + 1; symbol <= numSymbols; symbol++)
            {
                if (typestring[symbol] == null)
                {
                    untyped.add(symbol);
                    typestring[symbol] = nonterminalDefaultType;
                }
            }

            if (option.warnings && !untyped.isEmpty())
            {
                StringBuilder tok = new StringBuilder(Control.SYMBOL_SIZE);
                for (int i = 0, n = untyped.size(); i < n; i++)
                {
                    int symbol = untyped.get(i);

                    restoreSymbol(tok, retrieveString(symbol));
                    option.emitWarning(retrieveTokenLocation(symbol),
                            "No type specified for nonterminal \"" + tok + "\". " + nonterminalDefaultType + " assumed.");
                }
            }
        }

        //
        // If bad errors were detected, quit!
        //
        if (errorFlag)
            control.exit();

        //
        // Generate the output.
        //
        action.setupBuiltinMacros();
        action.generateDefaultTitle(noticeActions);
        action.processCodeActions(headerActions, typestring, processedRuleMap);
        if (option.automaticAst != Option.AutomaticAst.NONE)
        {
            assert (classnameSet.size() == classname.size());
            action.processAstActions(astActions, noticeActions, initialActions,
                    typestring, processedRuleMap, classnameSet, classname);
        }
        else
        {
            action.processCodeActions(initialActions, typestring, processedRuleMap);
            action.processCodeActions(codeActions, typestring, processedRuleMap);
        }
        action.processCodeActions(trailerActions, typestring, processedRuleMap);
        if (option.warnings)
            action.checkExportMacros();
        actionBlocks.flush(); // Print out all action buffers and close action files.

        //
        // Clear out unnecessary space
        //
        noticeActions.clear();
        headerActions.clear();
        initialActions.clear();
        codeActions.clear();
        astActions.clear();
        trailerActions.clear();
        attributeActions.clear();
        classnameSet.reset();
        classname.clear();
        specialNonterminalArray = null;
        processedRuleMap.clear();

        option.flushReport();

        if (action.errorFlag)
            control.exit();

        //
        // Restore the original rules... We no longer need the virtual rules
        // created for the prostheses.
        //
        rules.subList(saveRulesLength - 1, rules.size()).clear();           // restore the original rules
        RuleElement gate = new RuleElement();
        gate.rhsIndex = rhsSym.size();
        rules.add(gate);    // add a gate
        rhsSym.removeRange(saveRhsSymLength, rhsSym.size());
        parser.rules.subList(saveParserRulesLength, parser.rules.size()).clear();
    }

    VariableSymbol getSymbol(int symIndex)
    {
        int kind = lexStream.kind(symIndex);
        switch (kind)
        {
            case Lpgsym.TK_EMPTY_KEY:
                 return emptySymbol;
            case Lpgsym.TK_IDENTIFIER_KEY:
                 return identifierSymbol;
            case Lpgsym.TK_ERROR_KEY:
                 return errorSymbol;
            case Lpgsym.TK_EOF_KEY:
                 return eofSymbol;
            case Lpgsym.TK_EOL_KEY:
                 return eolSymbol;
            default:
                assert (kind == Lpgsym.TK_SYMBOL || kind == Lpgsym.TK_MACRO_NAME);
                return lexStream.getVariableSymbol(symIndex);
        }
    }

    private int getSymbolIndex(int index)
    {
        VariableSymbol symbol = getSymbol(index);
        return symbol != null ? symbol.symbolIndex() : 0;
    }

    private int assignSymbolIndex(VariableSymbol symbol)
    {
        int index = symbol.symbolIndex();
        if (index == 0)
        {
            index = ++numSymbols;
            symbol.setSymbolIndex(index);
            assert (index == symbolIndex.size());
            symbolIndex.add(new SymbolElement(symbol, symbol.nameIndex()));
        }

        return index;
    }

    //
    // Construct set of exported symbols. Make sure that there are no duplicates.
    //
    private void processExportedTerminals()
    {
        int[] importIndex = new int[variableTable.size()];
        Arrays.fill(importIndex, -1);

        //
        // Merge the exported symbols inherited from filter files
        // with the symbols that are to be locally exported.
        //
        IntArrayList imports = new IntArrayList();
        for (int i = 0, n = lexStream.numImportedFilters(); i < n; i++)
            imports.add(lexStream.importedFilter(i));
        for (int i = 0, n = parser.exports.size(); i < n; i++)
            imports.add(parser.exports.get(i));

        //
        // Add all symbols from the imports list to the exported_symbols list.
        //
        for (int i = 0, n = imports.size(); i < n; i++)
        {
            int imp = imports.get(i);
            VariableSymbol symbol = lexStream.getVariableSymbol(imp);
            int decl = importIndex[symbol.index()];
            if (decl == -1)
            {
                importIndex[symbol.index()] = imp;
                exportedSymbols.add(symbol);
            }
            else
            {
                StringBuilder msg = new StringBuilder("Symbol \"");
                msg.append(symbol.name());
                msg.append("\" was specified more than once in an Export section. Previous specification was located at ");
                msg.append(lexStream.fileName(decl));
                msg.append(':');
                msg.append(lexStream.line(decl));
                msg.append(':');
                msg.append(lexStream.column(decl));
                msg.append(':');
                msg.append(lexStream.endLine(decl));
                msg.append(':');
                msg.append(lexStream.endColumn(decl));
                msg.append(':');
                msg.append(lexStream.startLocation(decl));
                msg.append(':');
                msg.append(lexStream.endLocation(decl));
                option.emitWarning(imp, msg);
            }
        }
    }

    private void processTerminals()
    {
        //
        // We MUST first declare the terminals that are markers for the start
        // symbols. By declaring them first, the indexes of these markers in
        // the declared_terminals will be the same as the corresponding indexed
        // of the start symbols in parser.start_indexes. Note that the main start
        // symbol has the "empty" symbol as its marker. I.e., no marker is required
        // for it when parsing.
        //
        empty = assignSymbolIndex(emptySymbol);
        assert declaredTerminals.isEmpty();
        declaredTerminals.add(empty);
        for (int i = 1, n = parser.startIndexes.size(); i < n; i++) // Create the terminal markers for the extra entry points, if any.
        {
            String entryName = lexStream.nameString(parser.startIndexes.get(i));
            String markerName = entryName + "Marker";
            VariableSymbol markerSymbol = variableTable.find(markerName);
            for (int k = 0; markerSymbol != null && k < 100; k++) // keep searching until we find an undeclared symbol name
            {
                markerName = entryName + "Marker" + k;
                markerSymbol = variableTable.find(markerName);
            }

            if (markerSymbol == null)
            {
                 markerSymbol = new VariableSymbol(markerName, variableTable.size());
                 variableTable.insert(markerName, markerSymbol);
            }
            else
            {
                option.emitError(lexStream.getVariableSymbol(parser.startIndexes.get(i)).location(),
                        "Unable to declare a marker for symbol \"" + entryName +
                                "\". After trying the suffixes \"Marker\" and \"Marker\" + [\"00\"..\"99\"]");
                control.exit();
            }

            assert(declaredTerminals.size() == i);
            declaredTerminals.add(assignSymbolIndex(markerSymbol));
        }

        //
        // Process the declared set of terminal symbols.
        // This is useful for upward compatibility with old versions where
        // the declaration of terminals was required. It will also pick up
        // terminal symbols that are declared but never used.
        //
        for (int i = 0, n = parser.terminals.size(); i < n; i++)
        {
            VariableSymbol terminal = lexStream.getVariableSymbol(parser.terminals.get(i));
            assert (terminal != null);
            declaredTerminals.add(assignSymbolIndex(terminal));
        }

        BitSet importedTermSet = new BitSet(variableTable.size());
        for (int i = 0, n = lexStream.numImportedTerminals(); i < n; i++) // imported from another grammar
        {
            int imp = lexStream.importedTerminal(i);
            VariableSymbol terminal = lexStream.getVariableSymbol(imp);
            declaredTerminals.add(assignSymbolIndex(terminal));

            importedTermSet.set(terminal.index());
        }

        for (int i = 0, n = parser.keywords.size(); i < n; i++)
        {
            VariableSymbol keyword = lexStream.getVariableSymbol(parser.keywords.get(i));
            assert (keyword != null);
            declaredTerminals.add(assignSymbolIndex(keyword));
        }

        //
        // Compute the set of terminals directly from the grammar, even
        // though the user may have declared them using a terminals and keywords section;
        // Traverse the right-hand side and initially, add all symbols found to the
        // term_set.
        //
        BitSet termSet = new BitSet(variableTable.size());
        for (int i = 0, n = parser.rules.size(); i < n; i++)
        {
            for (int j = lexStream.next(parser.rules.get(i).separatorIndex);
                     j < parser.rules.get(i).endRhsIndex;
                     j = lexStream.next(j))
            {
                VariableSymbol symbol = lexStream.getVariableSymbol(j);
                if (symbol != null)
                    termSet.set(symbol.index());
            }
        }

        //
        // Add all symbols appearing on the right-hand side of an alias rule to the term_set.
        //
        for (int i = 0, n = parser.aliases.size(); i < n; i++)
        {
            VariableSymbol symbol = lexStream.getVariableSymbol(parser.aliases.get(i).rhsIndex); // the right-hand-side symbol
            if (symbol != null) // no symbol is defined for EMPTY
                termSet.set(symbol.index());
        }

        //
        // Remove all symbols appearing on the left-hand side of an alias rule from the term_set.
        //
        for (int i = 0, n = parser.aliases.size(); i < n; i++)
        {
            VariableSymbol symbol = lexStream.getVariableSymbol(parser.aliases.get(i).lhsIndex); // the left-hand-side symbol
            assert (symbol != null);
            termSet.clear(symbol.index());
        }

        //
        // Remove all symbols appearing on the left-hand side of a grammar rule from the term_set.
        //
        BitSet ntermSet = new BitSet(variableTable.size());
        for (int i = 0, n = parser.rules.size(); i < n; i++)
        {
            VariableSymbol symbol = lexStream.getVariableSymbol(parser.rules.get(i).lhsIndex);
            assert (symbol != null);
            termSet.clear(symbol.index());
            ntermSet.set(symbol.index());
        }

        //
        // Remove all symbols to whom a known non-terminal is aliased.
        // This iterative algorithm is in general INEFFICIENT. However, since
        // the Alias section is likely to be small, it does not matter.
        //
        // TODO: compute this more efficiently? ... first compute partial order
        //       of nonterminals based on Alias relation, etc...
        //
        boolean changed = true;
        while (changed)
        {
            changed = false;

            //
            // Remove all symbols to whom a known non-terminal is aliased.
            // I.e., any symbol appearing on the right-hand side of an alias rule
            // where the left-hand side symbol was also used as the left-hand
            // side of a grammar rule.
            //
            for (int i = 0, n = parser.aliases.size(); i < n; i++)
            {
                VariableSymbol symbol = lexStream.getVariableSymbol(parser.aliases.get(i).lhsIndex);
                assert (symbol != null);
                if (ntermSet.get(symbol.index())) // The lhs is a nonterminal?
                {
                    symbol = lexStream.getVariableSymbol(parser.aliases.get(i).rhsIndex); // the right-hand-side symbol
                    if (!ntermSet.get(symbol.index())) // The rhs is NOT a nonterminal?
                    {
                        assert (symbol != null);
                        changed = true;
                        termSet.clear(symbol.index());
                        ntermSet.set(symbol.index());
                   }
                }
            }
        }

        //
        // Add all recovery symbols that are not known to be nonterminals
        // to the term_set. Note that the recovery terminals are considered
        // to be implicitly imported.
        //
        for (int i = 0, n = parser.recovers.size(); i < n; i++)
        {
            VariableSymbol symbol = lexStream.getVariableSymbol(parser.recovers.get(i));
            if (!ntermSet.get(symbol.index()))
            {
                termSet.set(symbol.index());
                declaredTerminals.add(assignSymbolIndex(symbol));

                importedTermSet.set(symbol.index()); // considered implicitly imported.
            }
        }

        //
        // Now process the declared set of terminal symbols.
        //
        for (int i = 0, n = variableTable.size(); i < n; i++)
        {
            VariableSymbol symbol = variableTable.element(i);
            if (termSet.get(symbol.index()))
                assignSymbolIndex(symbol);
        }

        //
        // Now take care of the terminal aliasses that are not aliassed to special symbols.
        //
        IntArrayList remainingAliases = new IntArrayList();
        processInitialAliases(remainingAliases);

        //
        // Process the special symbols.
        //
        if (parser.identifierIndex != 0)
        {
            identifierSymbol = lexStream.getVariableSymbol(parser.identifierIndex);
            assert (identifierSymbol != null);
            identifierImage = assignSymbolIndex(identifierSymbol);
            declaredTerminals.add(identifierImage);
        }

        if (parser.eolIndex != 0)
        {
            eolSymbol = lexStream.getVariableSymbol(parser.eolIndex);
            assert (eolSymbol != null);
            eolImage = assignSymbolIndex(eolSymbol);
            declaredTerminals.add(eolImage);
        }

        if (parser.eofIndex != 0)
        {
            eofSymbol = lexStream.getVariableSymbol(parser.eofIndex);
            assert (eofSymbol != null);
        }
        else
        {
            eofSymbol = allocateVariableSymbol("eof");
        }
        eofImage = assignSymbolIndex(eofSymbol);
        declaredTerminals.add(eofImage);

        if (parser.errorIndex != 0)
        {
            errorSymbol = lexStream.getVariableSymbol(parser.errorIndex);
            assert (errorSymbol != null);
            errorImage = assignSymbolIndex(errorSymbol);
            declaredTerminals.add(errorImage);
        }
        else if (option.errorMaps)
        {
            errorSymbol = allocateVariableSymbol("error");
            errorImage = assignSymbolIndex(errorSymbol);
            declaredTerminals.add(errorImage);
        }
        else
            errorImage = DEFAULT_SYMBOL;

        //
        // Compute the set of keywords identified by the user.
        // Two representations are used: a bit set for ease of
        // queries and a tuple for ease of iteration.
        //
        keywordSet = new BitSet(numSymbols + 1);
        for (int i = 0, n = parser.keywords.size(); i < n; i++)
        {
            VariableSymbol keyword = lexStream.getVariableSymbol(parser.keywords.get(i));
            int keywordSymbolIndex = keyword.symbolIndex();
            if (!keywordSet.get(keywordSymbolIndex))
            {
                keywords.add(keywordSymbolIndex);
                keywordSet.set(keywordSymbolIndex);
            }
        }

        //
        // Compute the number of terminals in the language.
        // It is important that this operation be performed here!
        // Note that the next symbol following last terminal is the
        // Accept nonterminal.
        //
        numTerminals = numSymbols;
        acceptImage = assignSymbolIndex(acceptSymbol);

        processRemainingAliases(remainingAliases);

        //
        // Check to see if the grammar contained undeclared terminals and if so, print them.
        //
        BitSet declaredSet = new BitSet(numTerminals + 1);
        int numUndeclaredTerminals = numTerminals; // Assume that all terminals are not declared
        for (int i = 0, n = declaredTerminals.size(); i < n; i++)
        {
            int symbol = declaredTerminals.get(i);
            if (!declaredSet.get(symbol))
            {
                declaredSet.set(symbol);
                numUndeclaredTerminals--; // Each time we find a terminal declaration, decrease undeclared count
            }
        }
        if (numUndeclaredTerminals > 0) // Some terminals were not declared?
        {
            for (int symbol = 1; symbol <= numTerminals; symbol++)
            {
                if (!declaredSet.get(symbol))
                {
                    StringBuilder msg = new StringBuilder("The undeclared symbol \"");
                    restoreSymbol(msg, retrieveString(symbol));
                    msg.append("\" is assumed to be a terminal");
                    option.emitError(retrieveTokenLocation(symbol), msg);
                }
            }
        }

        //
        // Check to see if a terminal symbol that was used in this file
        // was not exported from one of the files imported by this grammar.
        //
        if (option.warnings && !option.importFile.isEmpty())
        {
            for (int i = 0, n = variableTable.size(); i < n; i++)
            {
                VariableSymbol symbol = variableTable.element(i);
                int symIdx = symbol.symbolIndex();
                if (termSet.get(symIdx) && !importedTermSet.get(symIdx))
                {
                    StringBuilder msg = new StringBuilder("The terminal symbol \"");
                    restoreSymbol(msg, retrieveString(symIdx));
                    msg.append("\" was not imported from");
                    if (option.importFile.size() == 1)
                    {
                        msg.append(' ');
                        msg.append(option.importFile.get(0));
                    }
                    else
                    {
                        msg.append(": ");
                        for (int j = 0, m = option.importFile.size(); j < m; j++)
                        {
                            if (j > 0)
                                msg.append(", ");
                            msg.append(option.importFile.get(j));
                        }
                    }
                    option.emitWarning(retrieveTokenLocation(symIdx), msg);
                }
            }
        }
    }

    private void processInitialAliases(IntArrayList remainingAliases)
    {
        for (int i = 0, n = parser.aliases.size(); i < n; i++)
        {
            int rhsIndex = parser.aliases.get(i).rhsIndex;
            int rhsKind = lexStream.kind(rhsIndex);

            if (rhsKind == Lpgsym.TK_EMPTY_KEY ||
                rhsKind == Lpgsym.TK_IDENTIFIER_KEY ||
                rhsKind == Lpgsym.TK_ERROR_KEY ||
                rhsKind == Lpgsym.TK_EOF_KEY ||
                rhsKind == Lpgsym.TK_EOL_KEY)
                remainingAliases.add(i);
            else
            {
                int image = getSymbolIndex(rhsIndex); // the right-hand-side symbol
                if (image == 0)
                    remainingAliases.add(i);
                else // If the right-hand side symbol is a declared symbol
                {
                    //
                    // Note that when one of the special symbols such as %eof,
                    // or %eol etc.. are used on the left side of an alias, they
                    // are preprocessed in the grammar as if they were specified
                    // in a separate section headed by the keyword in question.
                    // The use of these keywords on the left-hand side in the
                    // Alias section has been deprecated.
                    //
                    int lhsIndex = parser.aliases.get(i).lhsIndex;
                    assert (lexStream.kind(lhsIndex) == Lpgsym.TK_SYMBOL ||
                            lexStream.kind(lhsIndex) == Lpgsym.TK_MACRO_NAME);

                    VariableSymbol symbol = lexStream.getVariableSymbol(lhsIndex);
                    assert (symbol != null);
                    if (symbol.symbolIndex() != 0)
                    {
                        option.emitError(lhsIndex, "This symbol was previously defined");
                        errorFlag = true;
                    }
                    symbol.setSymbolIndex(image);
                }
            }
        }

        //
        // If bad errors were detected, quit!
        //
        if (errorFlag)
            control.exit();
    }

    private void processRemainingAliases(IntArrayList remainingAliases)
    {
        for (int i = 0, n = remainingAliases.size(); i < n; i++)
        {
            AliasDefinition aliasDef = parser.aliases.get(remainingAliases.get(i));
            int lhsIndex = aliasDef.lhsIndex;
            int rhsIndex = aliasDef.rhsIndex;
            int rhsKind = lexStream.kind(rhsIndex);

            if ((rhsKind == Lpgsym.TK_IDENTIFIER_KEY && identifierSymbol == null) ||
                (rhsKind == Lpgsym.TK_EOL_KEY && eolSymbol == null) ||
                (rhsKind == Lpgsym.TK_ERROR_KEY && errorSymbol == null))
            {
                option.emitError(rhsIndex,
                        lexStream.nameString(rhsIndex) + " can\'t be used as an alias for \"" +
                        lexStream.nameString(lhsIndex) + "\" because it was not defined.");

                errorFlag = true;
            }
            else
            {
                int image = getSymbolIndex(rhsIndex); // the right-hand-side symbol
                if (image == 0) // an as yet undefined symbol
                {
                    VariableSymbol symbol = lexStream.getVariableSymbol(rhsIndex);
                    assert (symbol != null);
                    image = assignSymbolIndex(symbol);
                }

                //
                // Note that when one of the special symbols such as %eof,
                // or %eol etc.. are used on the left side of an alias, they
                // are preprocessed in the grammar as if they were specified
                // in a separate section headed by the keyword in question.
                // The use of these keywords on the left-hand side in the
                // Alias section has been deprecated.
                //
                assert (lexStream.kind(lhsIndex) == Lpgsym.TK_SYMBOL ||
                        lexStream.kind(lhsIndex) == Lpgsym.TK_MACRO_NAME);

                VariableSymbol symbol = lexStream.getVariableSymbol(lhsIndex);
                assert (symbol != null);
                if (symbol.symbolIndex() != 0)
                {
                    option.emitError(lhsIndex, "This symbol was previously defined");
                    errorFlag = true;
                }
                symbol.setSymbolIndex(image);
            }
        }

        if (eolImage == 0)
            eolImage = eofImage;

        //
        // If bad errors were detected, quit!
        //
        if (errorFlag)
            control.exit();
    }

    private String insertInterface(LookupTable<String, Symbol> symbolSet, String name)
    {
        String typeName = "I" + name;
        symbolSet.findOrInsert(typeName, new Symbol(typeName, symbolSet.size()));

        return typeName;
    }

    private void processRules()
    {
        //
        // Add starting rule.
        //
        int startIndex = (parser.startIndexes.isEmpty() ? parser.rules.get(0).lhsIndex : parser.startIndexes.get(0));
        VariableSymbol start = lexStream.getVariableSymbol(startIndex);
        assignSymbolIndex(start);
        startSymbol.add(start);
        startImage = start.symbolIndex(); // save the image of the root nonterminal
        rules.add(new RuleElement(startIndex, startIndex, acceptImage,
        		Lpgsym.TK_EQUIVALENCE, Lpgsym.TK_EQUIVALENCE, 0, 0));

        if (start.symbolIndex() != empty)
            rhsSym.add(start.symbolIndex());
        else
        {
            option.emitError(startIndex, "The start symbol, \"" + start.name() + "\", is aliased to the Empty symbol");
            control.exit();
        }

        //
        // Now process the other entry points (extra start symbols), if any.
        //
        for (int i = 1, n = parser.startIndexes.size(); i < n; i++)
        {
            int entryIndex = parser.startIndexes.get(i);
            VariableSymbol entry = lexStream.getVariableSymbol(entryIndex);
            assignSymbolIndex(entry);
            startSymbol.add(entry);

            rules.add(new RuleElement(entryIndex, entryIndex, start.symbolIndex(),
                    Lpgsym.TK_EQUIVALENCE, Lpgsym.TK_EQUIVALENCE, rhsSym.size(), 0));

            if (entry.symbolIndex() != empty)
            {
                rhsSym.add(declaredTerminals.get(i));
                rhsSym.add(entry.symbolIndex());
            }
            else
            {
                option.emitError(entryIndex, "The start symbol, \"" + entry.name() + "\", is aliased to the Empty symbol");
                control.exit();
            }
        }

        //
        // Are there any notice action blocks?
        //
        for (int i = 0, n = parser.noticeBlocks.size(); i < n; i++)
        {
            int blockToken = parser.noticeBlocks.get(i);
            BlockSymbol block = lexStream.getBlockSymbol(blockToken);
            if (!option.actionBlocks().isIgnoredBlock(block.blockBegin()))
            {
                if (block != option.defaultBlock())
                {
                    option.emitError(blockToken, "Only default blocks may appear in a notice segment");
                    errorFlag = true;
                }

                noticeActions.add(new ActionBlockElement(0,
                        // does not matter - block must be default block...
                        ActionBlockElement.ActionBlockKind.INITIALIZE, blockToken, noticeBuffer));
            }
        }

        //
        // Now, process the title block, if present.
        // Then, process all global blocks specified.
        //
        for (int i = 0, n = parser.globalBlocks.size(); i < n; i++)
        {
            int blockToken = parser.globalBlocks.get(i);
            BlockSymbol block = lexStream.getBlockSymbol(blockToken);
            if (!option.actionBlocks().isIgnoredBlock(block.blockBegin()))
            {
                if (block != option.defaultBlock())
                {
                    option.emitError(blockToken, "Only default blocks may appear in a Title or Global segment");
                    control.exit();     //Phileas TODO: why not errorFlag = true; ?
                }

                StringBuilder buffer = block.buffer();
                headerActions.add(new ActionBlockElement(0,
                        // does not matter - block must be default block...
                        ActionBlockElement.ActionBlockKind.INITIALIZE, blockToken,
                        buffer != null ? buffer : block.actionFileSymbol().initialHeadersBuffer()));
            }
        }

        for (int i = 0, n = parser.initialBlocks.size(); i < n; i++)
        {
            int blockToken = parser.initialBlocks.get(i);
            BlockSymbol block = lexStream.getBlockSymbol(blockToken);
            if (!option.actionBlocks().isIgnoredBlock(block.blockBegin()))
            {
                StringBuilder buffer = block.buffer();
                initialActions.add(new ActionBlockElement(0,
                        ActionBlockElement.ActionBlockKind.BODY, blockToken,
                        buffer != null ? buffer : block.actionFileSymbol().bodyBuffer()));
            }
        }

        //
        // In this loop, the grammar is placed in the rule table structure and the
        // right-hand sides are placed in the RHS table.  A check is made to prevent
        // terminals from being used as left hand sides.
        // Also, if macro symbols are associated with symbols in the right-hand
        // side of certain rules, keep track of such macro names and their position
        // in processed_rule_map.
        //
        specialNonterminalArray = new IntArrayList[variableTable.size()];
        for (int i = 0; i < specialNonterminalArray.length; i++)
            specialNonterminalArray[i] = new IntArrayList();
        //
        // We now skip the elements of procesed_rule_map that correspond to the
        // "Start" rules as no macros can be associated with them and no AST is
        // allocated for them.
        //
        for (int i = 0, n = startSymbol.size(); i < n; i++)
            processedRuleMap.add(null);

        for (int i = 0, n = parser.rules.size(); i < n; i++)
        {
            List<ProcessedRuleElement> processedRuleElements = new ArrayList<ProcessedRuleElement>();
            processedRuleMap.add(processedRuleElements);
            RuleDefinition rule = parser.rules.get(i);
            VariableSymbol lhsVarSymbol = lexStream.getVariableSymbol(rule.lhsIndex);
            int lhsImage = assignSymbolIndex(lhsVarSymbol);
            int separatorTokenKind = lexStream.kind(rule.separatorIndex);

            if (isTerminal(lhsImage))
            {
                option.emitError(rule.lhsIndex, "Terminal symbol used as left hand side");
                errorFlag = true;
            }

            //
            // Find the last token in the right-hand side of the rule that is
            // a grammar symbol (not a block).
            //
            int lastSymbolIndex = rule.separatorIndex;
            for (int j = rule.endRhsIndex - 1; j > rule.separatorIndex; j = lexStream.previous(j))
            {
                int kind = lexStream.kind(j);
                if (kind == Lpgsym.TK_SYMBOL || kind == Lpgsym.TK_EMPTY_KEY)
                {
                    lastSymbolIndex = j;
                    break;
                }
            }

            //
            // Add info for this rule to rules (of RuleElement) array.
            //
            int ruleIndex = rules.size();
            RuleElement ruleElement = new RuleElement(rule.separatorIndex,
                    lastSymbolIndex, lhsImage, separatorTokenKind,
                    separatorTokenKind != Lpgsym.TK_OR_MARKER ? separatorTokenKind
                                                              : rules.get(ruleIndex - 1).producesTokenKind,
                    rhsSym.size(), i);
            rules.add(ruleElement);

            boolean ruleContainsActionBlock = false;
            for (int j = lexStream.next(rule.separatorIndex); j < rule.endRhsIndex; j = lexStream.next(j))
            {
                if (lexStream.kind(j) == Lpgsym.TK_BLOCK)
                {
                    BlockSymbol block = lexStream.getBlockSymbol(j);
                    if (!option.actionBlocks().isIgnoredBlock(block.blockBegin()))
                    {
                        ruleContainsActionBlock = true;

                        //
                        // Check whether or not the rule is a single production
                        // and if so, issue an error and stop.
                        //
                        if (ruleElement.isArrowProduction())
                            option.emitError(j, "This action is associated with a single production");

                        if ((!option.attributes) || j > lastSymbolIndex)
                        {
                            StringBuilder buffer = block.buffer();

                            ActionBlockElement action = new ActionBlockElement(ruleIndex,
                                    ActionBlockElement.ActionBlockKind.BODY, j,
                                    buffer != null ? buffer : block.actionFileSymbol().bodyBuffer());
                            if (option.automaticAst != Option.AutomaticAst.NONE)
                                astActions.add(action);
                            else
                                codeActions.add(action);
                        }
                        else
                        {
                            int index = attributeActions.size();
                            String name = String.valueOf(option.escape) + index;
                            VariableSymbol lhsSymbol = variableTable.find(name);
                            if (lhsSymbol != null)
                            {
                                // TODO: GENERATE ERROR MESSAGE !!!
                                assert false;
                            }
                            else
                            {
                                lhsSymbol = new VariableSymbol(name, variableTable.size());
                                variableTable.insert(name, lhsSymbol);
                            }

                            AttributeElement attrAction = new AttributeElement(assignSymbolIndex(lhsSymbol), j);
                            attributeActions.add(attrAction);

                            rhsSym.add(attrAction.lhsImage);
                        }
                    }
                }
                else if (lexStream.kind(j) == Lpgsym.TK_MACRO_NAME)
                {
                    int image = getSymbolIndex(j - 1); // preceding symbol
                    assert (image != 0);

                    //
                    // This may occur if the first symbol appearing in a rule was aliased to
                    // %empty.
                    //
                    if (image == empty)
                    {
                        option.emitError(j, "Misplaced rule macro associated with Empty symbol");
                        errorFlag = true;
                    }

                    //
                    // We have two cases to consider here. Either the empty macro, $,
                    // was specified or a named macro was specified.
                    //
                    // 1. A named macro was specified:
                    //    a. If there was a definition already generated automatically for
                    //       that symbol, we simply need to override it with the user's
                    //       definition.
                    //
                    //    b. otherwise, we create a new definition for the macro
                    //
                    // 2. The empty macro ($) was specified:
                    //    if there was a definition already generated for that symbol,
                    //    remove it.
                    //
                    int position = rhsSym.size() - ruleElement.rhsIndex;
                    if (lexStream.nameString(j).length() > 1)
                    {
                        if ((option.variables == Option.Variables.BOTH) ||
                            (option.variables == Option.Variables.TERMINALS && isTerminal(image)) ||
                            (option.variables == Option.Variables.NON_TERMINALS && !isTerminal(image))) // override previous definition
                        {
                            ProcessedRuleElement processedRuleElement =
                                    processedRuleElements.get(processedRuleElements.size() - 1);
                            processedRuleElement.image = image;
                            processedRuleElement.tokenIndex = j;
                            assert (processedRuleElement.position == position);
                        }
                        else // add new definition
                        {
                            processedRuleElements.add(new ProcessedRuleElement(image, j, position));
                        }
                    }
                    else if ((option.variables == Option.Variables.BOTH) ||
                             (option.variables == Option.Variables.TERMINALS && isTerminal(image)) ||
                             (option.variables == Option.Variables.NON_TERMINALS && !isTerminal(image))) // remove previous definition
                    {
                        ProcessedRuleElement processedRuleElement =
                                processedRuleElements.remove(processedRuleElements.size() - 1);
                        assert (processedRuleElement.position == position);
                    }
                }
                else
                {
                    VariableSymbol rhsSymbol = getSymbol(j);
                    int image = (rhsSymbol != null ? assignSymbolIndex(rhsSymbol) : 0);
                    if (image == 0)
                    {
                         option.emitError(j, "This symbol was neither declared nor aliased to a terminal symbol");
                         errorFlag = true;
                    }
                    else if (image == eofImage)
                    {
                         option.emitError(j, "End-of-file symbol cannot be used in a rule");
                         errorFlag = true;
                    }
                    else if (image != empty)
                    {
                        rhsSym.add(image);

                        if ((option.variables == Option.Variables.BOTH) ||
                             (option.variables == Option.Variables.TERMINALS && isTerminal(image)) ||
                             (option.variables == Option.Variables.NON_TERMINALS && !isTerminal(image)))
                        {
                            int position = rhsSym.size() - ruleElement.rhsIndex;
                            processedRuleElements.add(new ProcessedRuleElement(image, j, position));
                        }
                    }
                }
            }

            if (ruleElement.isArrowProduction() && (ruleElement.rhsIndex + 1 == rhsSym.size()))
                numSingleProductions++;

            //
            // Keep track of all pairs (nonterminal X array_type) that are associated with
            // a rule that contain actions.
            //
            int arrayElementTypeIndex = parser.rules.get(i).arrayElementTypeIndex;
            if (arrayElementTypeIndex != 0 && ruleContainsActionBlock)
            {
                VariableSymbol arrayElementTypeSymbol = lexStream.getVariableSymbol(arrayElementTypeIndex);
                IntArrayList spNtArray = specialNonterminalArray[arrayElementTypeSymbol.index()];
                if (!spNtArray.contains(lhsImage))
                    spNtArray.add(lhsImage);
            }
        }

        //
        // If we have to generate extra rules because of the presence of
        // of "attributes", we do so here. Note that we don't have to check
        // here whether or not these attribute actions are to be ignored because
        // that check has already been made and if they were to be ignored they
        // would not have been added to this list in the first place.
        //
        for (int i = 0, n = attributeActions.size(); i < n; i++)
        {
            rules.add(new RuleElement(0, 0, attributeActions.get(i).lhsImage,
                    Lpgsym.TK_EQUIVALENCE, 0, rhsSym.size(), 0));
            processedRuleMap.add(null); // allocate an empty macro table for rule_index

            int blockToken = attributeActions.get(i).blockToken;
            BlockSymbol block = lexStream.getBlockSymbol(blockToken);
            StringBuilder buffer = block.buffer();
            codeActions.add(new ActionBlockElement(rules.size() - 1,
                                    ActionBlockElement.ActionBlockKind.BODY, blockToken,
                                    buffer != null ? buffer : block.actionFileSymbol().bodyBuffer()));
        }

        //
        // add a gate marker for the last rule.
        //
        rules.add(new RuleElement(0, 0, 0, 0, 0, rhsSym.size(), 0));

        //
        // Are there any trailing action blocks following the last rule?
        //
        for (int i = 0, n = parser.trailerBlocks.size(); i < n; i++)
        {
            int blockToken = parser.trailerBlocks.get(i);
            BlockSymbol block = lexStream.getBlockSymbol(blockToken);
            if (!option.actionBlocks().isIgnoredBlock(block.blockBegin()))
            {
                StringBuilder buffer = block.buffer();

                //
                // Note that trailer actions are associated with rule 0 instead of
                // the last rule (parser.rules.Length()). This is because we do not
                // want to have the variable name macros defined for the last rule
                // to be visible when processing trailer actions. (See ProcessCodeActions).
                //
                trailerActions.add(new ActionBlockElement(0,
                        ActionBlockElement.ActionBlockKind.FINALIZE, blockToken,
                        buffer != null ? buffer : block.actionFileSymbol().finalTrailersBuffer()));
            }
        }

        numNonterminals = numSymbols - numTerminals;
        numRules = startSymbol.size() + (parser.rules.size() - 1) + attributeActions.size();
        numItems = rhsSym.size() + (numRules + 1);

        assert (numRules == rules.size() - 2);

        //
        // Update the special_nonterminal_array map with information from the prostheses
        // rules prior to generating the classnames.
        //
        for (int i = 0, n = parser.prostheses.size(); i < n; i++)
        {
            List<ProcessedRuleElement> processedRuleElements = new ArrayList<ProcessedRuleElement>();
            processedRuleMap.add(processedRuleElements);
            RuleDefinition prosthesis = parser.prostheses.get(i);
            VariableSymbol lhsSymbol = lexStream.getVariableSymbol(prosthesis.lhsIndex);
            int lhsImage = lhsSymbol.symbolIndex();
            if (isTerminal(lhsImage)) // error that will be processed later.
                continue;

            int idx = lexStream.previous(prosthesis.endRhsIndex);
            if (lexStream.kind(idx) == Lpgsym.TK_BLOCK)
            {
                //
                // Keep track of all pairs (nonterminal X array_type) that are associated with
                // a rule that contain actions.
                //
                int arrayElementTypeIndex = prosthesis.arrayElementTypeIndex;
                if (arrayElementTypeIndex != 0)
                {
                    VariableSymbol arrayElementTypeSymbol = lexStream.getVariableSymbol(arrayElementTypeIndex);
                    IntArrayList spNtArray = specialNonterminalArray[arrayElementTypeSymbol.index()];
                    if (!spNtArray.contains(lhsImage))
                        spNtArray.add(lhsImage);
                }
            }
        }

        //
        // Compute the set of "recover" symbols identified by the user
        // using a bit set. Note that the Universe of this set is the
        // complete set of symbols though only terminal symbols are
        // allowed. This allows this bit set to be indexed by any symbol
        // without having to explicitly check that the symbol in question
        // is a terminal.
        //
        recoverSet = new BitSet(numSymbols + 1);
        for (int i = 0, n = parser.recovers.size(); i < n; i++)
        {
            VariableSymbol recover = lexStream.getVariableSymbol(parser.recovers.get(i));
            recoverSet.set(recover.symbolIndex());
        }

        //
        // In this loop, we construct the LHS_RULE map which maps
        // each non-terminal symbol into the set of rules it produces
        //
        lhsRule = new BoundedArray<IntArrayList>(numTerminals + 1, numSymbols);
        for (int i = lhsRule.Lbound(); i <= lhsRule.Ubound(); i++)
            lhsRule.set(i, new IntArrayList());
        for (int ruleNo = firstRule(); ruleNo <= lastRule(); ruleNo++)
        {
            int lhsSymbol = rules.get(ruleNo).lhs;
            lhsRule.get(lhsSymbol).add(ruleNo);
        }

        //
        // If bad errors were detected, quit!
        //
        if (errorFlag)
            control.exit();
    }

    private void setName(VariableSymbol symbol, VariableSymbol symbolName)
    {
        setName(symbol, symbolName, false);
    }

    private void setName(VariableSymbol symbol, VariableSymbol symbolName, boolean negate)
    {
        int index = name.size();
        name.add(symbolName.name());
        symbol.setNameIndex(index);

        int image = symbol.symbolIndex();
        symbolIndex.get(image).externalNameIndex = (negate ? -index : index);
    }

    private void processNames()
    {
        if (option.errorMaps)
        {
            //
            // We take care of accept first to that he gets assigned #0
            // which we can ignore later.
            //
            setName(acceptSymbol, nullSymbol);

            for (int i = 0, n = parser.names.size(); i < n; i++)
            {
                NameDefinition name = parser.names.get(i);
                VariableSymbol symbol = getSymbol(name.lhsIndex);
                int image = (symbol != null ? symbol.symbolIndex() : 0);
                if (image == 0)
                {
                    option.emitError(name.lhsIndex, "Undefined symbol");
                    errorFlag = true;
                }
                else if (symbolIndex.get(image).externalNameIndex != Util.OMEGA)
                {
                    option.emitError(name.lhsIndex, "This symbol has been named more than once");
                    errorFlag = true;
                }
                VariableSymbol rhsSymbol = getSymbol(name.rhsIndex);
                assert (rhsSymbol != null);
                setName(symbol, rhsSymbol);
            }

            if (errorImage == DEFAULT_SYMBOL) // some default, anything!
                symbolIndex.get(DEFAULT_SYMBOL).externalNameIndex = symbolIndex.get(acceptImage).externalNameIndex;

            for (int i = firstTerminal(); i <= lastTerminal(); i++)
            {
                if (symbolIndex.get(i).externalNameIndex == Util.OMEGA)
                    setName(symbolIndex.get(i).symbol, symbolIndex.get(i).symbol);
            }

            for (int i = firstNonTerminal(); i <= lastNonTerminal(); i++)
            {
                if (symbolIndex.get(i).externalNameIndex == Util.OMEGA)
                {
                    if (option.names == Option.Names.MAXIMUM)
                         setName(symbolIndex.get(i).symbol, symbolIndex.get(i).symbol);
                    else if (option.names == Option.Names.OPTIMIZED)
                         setName(symbolIndex.get(i).symbol, symbolIndex.get(i).symbol, true);
                    else
                    {
                        assert (option.names == Option.Names.MINIMUM);
                        symbolIndex.get(i).externalNameIndex = symbolIndex.get(errorImage).externalNameIndex;
                    }
                }
            }

            //
            // If bad errors were detected, quit!
            //
            if (errorFlag)
                control.exit();
        }

        numNames = name.size() - 1;
    }

    //
    // This procedure takes two character strings as arguments: IN and OUT.
    // IN identifies a grammar symbol or name that is checked as to whether
    // or not it needs to be quoted. If so, the necessary quotes are added
    // as IN is copied into the space identified by OUT.
    // NOTE that it is assumed that IN and OUT do not overlap each other.
    //
    void restoreSymbol(StringBuilder out, String in)
    {
        out.delete(0, out.length());
        int len = in.length();
        if (len > 0)
        {
            char c0 = in.charAt(0);
            if ((len == 1 && c0 == option.orMarker) ||
                (c0 == option.escape)               ||
                (c0 == '\'')                        ||
                (in.charAt(len - 1) == '\'')        ||
                (in.indexOf(' ') != -1 &&
                (c0 != '<' || in.charAt(len - 1) != '>')))
            {
                out.append('\'');
                for (int i = 0; i < len; i++)
                {
                    char c = in.charAt(i);
                    if (c == '\'')
                        out.append("''");
                    else
                        out.append(c);
                }
                out.append('\'');

                return;
            }
        }

        out.append(in);
    }

    String restoreSymbol(String in)
    {
        int len = in.length();
        if (len > 0)
        {
            char c0 = in.charAt(0);
            if ((len == 1 && c0 == option.orMarker) ||
                (c0 == option.escape)               ||
                (c0 == '\'')                        ||
                (in.charAt(len - 1) == '\'')        ||
                (in.indexOf(' ') != -1 &&
                (c0 != '<' || in.charAt(len - 1) != '>')))
            {
                StringBuilder out = new StringBuilder();
                out.append('\'');
                for (int i = 0; i < len; i++)
                {
                    char c = in.charAt(i);
                    if (c == '\'')
                        out.append("''");
                    else
                        out.append(c);
                }
                out.append('\'');

                return new String(out);
            }
        }

        return in;
    }

    //
    // PRINT_LARGE_TOKEN generates code to print a token that may exceed the
    // limit of its field.  The argument are LINE which is the symbol a varying
    // length character string, TOKEN which is the symbol to be printed, INDENT
    // which is a character string to be used as an initial prefix to indent the
    // output line, and LEN which indicates the maximum number of characters that
    // can be printed on a given line.  At the end of this process, LINE will
    // have the value of the remaining substring that can fit on the output line.
    // If a TOKEN is too large to be indented in a line, but not too large for
    // the whole line, we forget the indentation, and printed it. Otherwise, it
    // is "chapped up" and printed in pieces that are each indented.
    //
    void printLargeToken(StringBuilder line, CharSequence token, CharSequence indent, int len)
    {
        try
        {
            int toklen = token.length();
            if (toklen > len && toklen <= Control.PRINT_LINE_SIZE - 1)
            {
                option.syslis.write(Util.LINE_SEPARATOR);
                option.syslis.write(token.toString());
                line.append(indent);
            }
            else
            {
                for (; toklen > len; toklen = token.length())
                {
                    option.syslis.write(Util.LINE_SEPARATOR);
                    option.syslis.write(token.subSequence(0, len).toString());
                    token = token.subSequence(len, toklen);
                }
                line.append(indent);
                line.append(token);
            }
        }
        catch (IOException e)
        {
        }
    }

    //
    //   If a listing is requested, this prints all the macros(if any), followed
    // by the aliases(if any), followed by the terminal symbols, followed by the
    // rules.
    //   This grammar information is printed on lines no longer than
    // PRINT_LINE_SIZE characters long.  If all the symbols in a rule cannot fit
    // on one line, it is continued on a subsequent line beginning at the
    // position after the equivalence symbol (::= or ->) or the middle of the
    // print_line, whichever is smaller.  If a symbol cannot fit on a line
    // beginning at the proper offset, it is laid out on successive lines,
    // beginning at the proper offset.
    //
    void processGrammarOutput(Action action)
    {
        if (!option.list)
            return;

        GrammarOutput output = new GrammarOutput();
        output.setParameters(control, action);
        output.run();
    }

    private VariableSymbol allocateVariableSymbol(String keyword)
    {
        return new VariableSymbol(option.escape + keyword, 0);
    }

    //
    // Buffer containing "notice" string to be output.
    //
    StringBuilder noticeBuffer()
    {
        return noticeBuffer;
    }

    IntArrayList rulesOfLhs(int nt)
    {
        return lhsRule.get(nt);
    }

    int ruleNumberOf(int ruleNo)
    {
        IntArrayList rulesOfLhs = lhsRule.get(rules.get(ruleNo).lhs);
        for (int i = 0, n = rulesOfLhs.size(); i < n; i++)
            if (rulesOfLhs.get(i) == ruleNo)
                return i + 1;
        throw new AssertionError();
    }

    boolean isTerminal(int x)
    {
        return x > 0 && x <= numTerminals;
    }

    boolean isNonTerminal(int x)
    {
        return x > numTerminals && x <= numSymbols;
    }

    boolean isKeyword(int x)
    {
        return isTerminal(x) && keywordSet.get(x);
    }

    BitSet keywordSet()
    {
        return keywordSet;
    }

    boolean isRecover(int x)
    {
        return recoverSet.get(x);
    }

    BitSet recoverSet()
    {
        return recoverSet;
    }

    int rhsSize(int ruleNo)
    {
        return rules.get(ruleNo + 1).rhsIndex - rules.get(ruleNo).rhsIndex;
    }

    boolean isUnitProduction(int ruleNo)
    {
        return rules.get(ruleNo).isArrowProduction() && rhsSize(ruleNo) == 1;
    }

    Token retrieveTokenLocation(int i)
    {
        return symbolIndex.get(i).symbol.location();
    }

    LexStream getLexStream()
    {
        return lexStream;
    }

    String retrieveString(int i)
    {
        return symbolIndex.get(i).symbol.name();
    }

    String retrieveName(int i)
    {
        return name.isEmpty() ? "" : name.get(symbolIndex.get(i).externalNameIndex);
    }

    int firstRhsIndex(int ruleNo)
    {
        return rules.get(ruleNo).rhsIndex;
    }

    int endRhsIndex(int ruleNo)
    {
        return rules.get(ruleNo + 1).rhsIndex;
    }

    int firstTerminal()
    {
        return 1;
    }

    int lastTerminal()
    {
        return numTerminals;
    }

    int firstNonTerminal()
    {
        return numTerminals + 1;
    }

    int lastNonTerminal()
    {
        return numSymbols;
    }

    int firstRule()
    {
        return 0;
    }

    int lastRule()
    {
        return numRules;
    }

    final Parser parser;

    int numItems = 0;
    int numSymbols = 0;
    int numSingleProductions = 0;
    int numTerminals = 0;
    int numNonterminals = 0;
    int numNames = 0;
    int numRules = 0;

    int empty = 0;
    int identifierImage = 0;
    int eolImage = 0;
    int eofImage = 0;
    int errorImage = 0;
    int acceptImage = 0;
    int startImage = 0;

    static final int DEFAULT_SYMBOL = 0;

    //
    // SYMBOL_INDEX is an array that maps symbol numbers to actual symbols.
    //
    final List<SymbolElement> symbolIndex;
    final List<RuleElement> rules   = new ArrayList<RuleElement>(2048); //Phileas TODO: is this size correct for java?
    final IntArrayList rhsSym       = new IntArrayList(8192);           //Phileas TODO: is this size correct for java?
    final IntArrayList keywords     = new IntArrayList();
          int[] prosthesisRuleIndex;
          List<String> name;
    final List<VariableSymbol> exportedSymbols      = new ArrayList<VariableSymbol>();
    final List<PairElement> checkPredecessorSetsFor = new ArrayList<PairElement>();

          BoundedArray<IntArrayList> lhsRule;

    private boolean errorFlag = false;

    private final List<ActionBlockElement> noticeActions        = new ArrayList<ActionBlockElement>();
    private final List<ActionBlockElement> headerActions        = new ArrayList<ActionBlockElement>();
    private final List<ActionBlockElement> initialActions       = new ArrayList<ActionBlockElement>();
    private final List<ActionBlockElement> codeActions          = new ArrayList<ActionBlockElement>();
    private final List<ActionBlockElement> astActions           = new ArrayList<ActionBlockElement>();
    private final List<ActionBlockElement> trailerActions       = new ArrayList<ActionBlockElement>();
    private final List<AttributeElement> attributeActions       = new ArrayList<AttributeElement>();
    private final LookupTable<String, Symbol> classnameSet              = new LookupTable<String, Symbol>();
    private final List<ClassnameElement> classname              = new ArrayList<ClassnameElement>();
    private       IntArrayList[] specialNonterminalArray        = null;
    private final List<List<ProcessedRuleElement>> processedRuleMap = new ArrayList<List<ProcessedRuleElement>>();

    private StringBuilder noticeBuffer  = new StringBuilder();
    private BitSet keywordSet           = null;
    private BitSet recoverSet           = null;

    private final IntArrayList declaredTerminals = new IntArrayList();

    //
    // The variables below are used to hold information about special
    // grammar symbols.
    //
    final List<VariableSymbol> startSymbol  = new ArrayList<VariableSymbol>();
    private final VariableSymbol emptySymbol;
    private VariableSymbol identifierSymbol = null;
    private VariableSymbol eolSymbol        = null;
    private VariableSymbol eofSymbol        = null;
    private VariableSymbol errorSymbol      = null;
    private final VariableSymbol acceptSymbol;
    private final VariableSymbol nullSymbol;

    private final Control control;
    private final Option option;
    private final Blocks actionBlocks;
    private final LexStream lexStream;
    private final LookupTable<String, VariableSymbol> variableTable;
    private final LookupTable<String, MacroSymbol> macroTable;

    private String astTokenClassname = null;
    private int astTokenInterface = 0;

    static class PairElement
    {
        PairElement(int leftSymbol, int rightSymbol)
        {
            this.leftSymbol     = leftSymbol;
            this.rightSymbol    = rightSymbol;
        }

        final int leftSymbol;
        final int rightSymbol;
    };

    static class SymbolElement
    {
        SymbolElement(VariableSymbol symbol, int externalNameIndex)
        {
            this.symbol             = symbol;
            this.externalNameIndex  = externalNameIndex;
        }

        final VariableSymbol symbol;
              int externalNameIndex;
    }

    static class AttributeElement
    {
        AttributeElement(int lhsImage, int blockToken)
        {
            this.lhsImage   = lhsImage;
            this.blockToken = blockToken;
        }

        private final int lhsImage;
        private final int blockToken;
    }

    //
    // RULES is the structure that contain the rules of the grammar.
    // Every rule of the grammar is mapped into an integer, and given
    // rule, and we have access to a value RHS which is the index
    // location in the vector RHS where the right-hand-side of the rule
    // begins.  The right hand side of a certain rule represented by an
    // integer I starts at index location RULES[I].RHS in RHS, and
    // ends at index location RULES[I + 1].RHS - 1.  An extra
    // NUM_RULES + 1 element is used as a "fence" for the last rule.
    // The RHS vector as mentioned above is used to hold a complete
    // list of all the right-hand-side symbols specified in the grammar.
    //
    static class RuleElement
    {
        RuleElement()
        {
        }

        RuleElement(int firstTokenIndex, int lastTokenIndex, int lhs,
                int separatorTokenKind, int producesTokenKind, int rhsIndex, int sourceIndex)
        {
            this.firstTokenIndex    = firstTokenIndex;
            this.lastTokenIndex     = lastTokenIndex;
            this.lhs                = lhs;
            this.separatorTokenKind = separatorTokenKind;
            this.producesTokenKind  = producesTokenKind;
            this.rhsIndex           = rhsIndex;
            this.sourceIndex        = sourceIndex;
        }

        int firstTokenIndex; // The index of the separator of the rule
        int lastTokenIndex;  // the last token in the rule
        int lhs;
        int separatorTokenKind;
        int rhsIndex;
        int sourceIndex; // the index in the parser object of this rule.

        //
        // In case an or delimiter, '|', is used, we may still want
        // to know what equivalence symbol started the chain.
        //
        int producesTokenKind;

        boolean isPriorityProduction()
        {
            return producesTokenKind == Lpgsym.TK_PRIORITY_ARROW ||
                   producesTokenKind == Lpgsym.TK_PRIORITY_EQUIVALENCE;
        }

        boolean isArrowProduction()
        {
            return producesTokenKind == Lpgsym.TK_ARROW ||
                   producesTokenKind == Lpgsym.TK_PRIORITY_ARROW;
        }

        boolean isAlternateProduction()
        {
            return separatorTokenKind == Lpgsym.TK_OR_MARKER;
        }
    }

}
